namespace OneWorks.Utils.FluentData
{
    using System;
    using System.Data;

    internal class ExecuteQueryHandler
    {
        private readonly DbCommand _command;
        private bool _queryAlreadyExecuted;

        public ExecuteQueryHandler(DbCommand command)
        {
            this._command = command;
        }

        internal void ExecuteQuery(bool useReader, Action action)
        {
            try
            {
                this.PrepareDbCommand(useReader);
                action();

                if (this._command.Data.Context.Data.OnExecuted != null)
                    this._command.Data.Context.Data.OnExecuted(new CommandEventArgs(this._command.Data.InnerCommand));
            }
            catch(Exception exception)
            {
                this.HandleQueryException(exception);
            }
            finally
            {
                this.HandleQueryFinally();
            }
        }

        private void PrepareDbCommand(bool useReader)
        {
            if (this._queryAlreadyExecuted)
            {
                if(this._command.Data.UseMultipleResultsets)
                    this._command.Data.Reader.NextResult();
                else
                    throw new FluentDataException("A query has already been executed on this command object. Please create a new command object.");
            }
            else
            {
                this._command.Data.InnerCommand.CommandText = this._command.Data.Sql.ToString();

                if(this._command.Data.Context.Data.CommandTimeout != Int32.MinValue)
                    this._command.Data.InnerCommand.CommandTimeout = this._command.Data.Context.Data.CommandTimeout;

                if(this._command.Data.InnerCommand.Connection.State != ConnectionState.Open)
                    this.OpenConnection();

                if(this._command.Data.Context.Data.UseTransaction)
                {
                    if(this._command.Data.Context.Data.Transaction == null)
                        this._command.Data.Context.Data.Transaction = this._command.Data.Context.Data.Connection.BeginTransaction((System.Data.IsolationLevel)this._command.Data.Context.Data.IsolationLevel);

                    this._command.Data.InnerCommand.Transaction = this._command.Data.Context.Data.Transaction;
                }

                if(this._command.Data.Context.Data.OnExecuting != null)
                    this._command.Data.Context.Data.OnExecuting(new CommandEventArgs(this._command.Data.InnerCommand));

                if (useReader)
                    this._command.Data.Reader = new DataReader(this._command.Data.InnerCommand.ExecuteReader());

                this._queryAlreadyExecuted = true;
            }
        }

        private void OpenConnection()
        {
            if(this._command.Data.Context.Data.OnConnectionOpening != null)
                this._command.Data.Context.Data.OnConnectionOpening(new ConnectionEventArgs(this._command.Data.InnerCommand.Connection));

            this._command.Data.InnerCommand.Connection.Open();

            if(this._command.Data.Context.Data.OnConnectionOpened != null)
                this._command.Data.Context.Data.OnConnectionOpened(new ConnectionEventArgs(this._command.Data.InnerCommand.Connection));
        }

        private void HandleQueryFinally()
        {
            if(!this._command.Data.UseMultipleResultsets)
            {
                if(this._command.Data.Reader != null)
                    this._command.Data.Reader.Close();

                this._command.ClosePrivateConnection();
            }
        }

        private void HandleQueryException(Exception exception)
        {
            if(this._command.Data.Reader != null)
                this._command.Data.Reader.Close();

            this._command.ClosePrivateConnection();
            if(this._command.Data.Context.Data.UseTransaction)
                this._command.Data.Context.CloseSharedConnection();

            if(this._command.Data.Context.Data.OnError != null)
                this._command.Data.Context.Data.OnError(new ErrorEventArgs(this._command.Data.InnerCommand, exception));
			
            throw exception;
        }
    }
}